import Cesium, { Ellipsoid, Geometry } from '../Ces/Cesium';

interface WeatherVolumeWallAxisGeometryOptions {
  positions: number[];
  maximumHeights?: number[];
  minimumHeights?: number[];
  granularity?: number;
  ellipsoid?: Ellipsoid;
  floors: number[];
  split?: boolean;
}

class WeatherVolumeWallAxisGeometry {
  private _positions: number[];
  private _floors: number[];
  private _split: boolean;
  private _minimumHeights?: number[];
  private _maximumHeights?: number[];
  private _granularity: number;
  private _ellipsoid: Ellipsoid;

  constructor(options: WeatherVolumeWallAxisGeometryOptions) {
    options = options ?? Cesium.Frozen.EMPTY_OBJECT

    const wallPositions = options.positions;
    const maximumHeights = options.maximumHeights;
    const minimumHeights = options.minimumHeights;

    //>>includeStart('debug', pragmas.debug);
    if (!Cesium.defined(wallPositions)) {
      throw new Cesium.DeveloperError('options.positions is required.');
    }
    if (
      Cesium.defined(maximumHeights)
      && maximumHeights.length !== wallPositions.length
    ) {
      throw new Cesium.DeveloperError(
        'options.positions and options.maximumHeights must have the same length.'
      );
    }
    if (
      Cesium.defined(minimumHeights)
      && minimumHeights.length !== wallPositions.length
    ) {
      throw new Cesium.DeveloperError(
        'options.positions and options.minimumHeights must have the same length.'
      );
    }
    //>>includeEnd('debug');

    const granularity =
      options.granularity ??
      Cesium.Math.RADIANS_PER_DEGREE

    const ellipsoid = options.ellipsoid ?? Cesium.Ellipsoid.WGS84

    this._positions = wallPositions;
    this._floors = options.floors;
    this._split = options.split ?? true
    this._minimumHeights = minimumHeights;
    this._maximumHeights = maximumHeights;
    this._granularity = granularity;
    this._ellipsoid = Cesium.Ellipsoid.clone(ellipsoid);
  }

  static computePositions(
    ellipsoid: Ellipsoid,
    wallPositions: number[],
    floors: number[],
    granularity: number
  ): { floorPositions: Float64Array[]; numCorners: number } {
    const length = wallPositions.length;
    const numCorners = length - 2;

    const minDistance = Cesium.Math.chordLength(
      granularity,
      ellipsoid.maximumRadius
    );

    const widthLength = wallPositions.length;

    const heightArray: number[][] = [];

    for (let i = 0; i < floors.length; i++) {
      const height = floors[i];
      const heights = new Array(widthLength);

      for (let j = 0; j < widthLength; j++) {
        heights[j] = height;
      }
      heightArray.push(heights);
    }

    const generateArcOptions: {
      positions: number[] | undefined;
      height: number[] | undefined;
      granularity: number | undefined;
      ellipsoid: Ellipsoid | undefined;
      minDistance: number;
    } = {
      positions: undefined,
      height: undefined,
      granularity: undefined,
      ellipsoid: undefined,
      minDistance
    };

    generateArcOptions.ellipsoid = ellipsoid;
    generateArcOptions.positions = wallPositions;

    const floorPositions: Float64Array[] = [];

    for (let i = 0; i < heightArray.length; i++) {
      generateArcOptions.height = heightArray[i];
      const floorPos = new Float64Array(
        Cesium.PolylinePipeline.generateArc(generateArcOptions)
      );

      floorPositions.push(floorPos);
    }

    return {
      floorPositions,
      numCorners
    };
  }

  static createGeometry(wallGeometry: WeatherVolumeWallAxisGeometry): Geometry | undefined {
    const scratchCartesian3Position1 = new Cesium.Cartesian3();
    const scratchCartesian3Position2 = new Cesium.Cartesian3();

    const wallPositions = wallGeometry._positions;
    const granularity = wallGeometry._granularity;
    const ellipsoid = wallGeometry._ellipsoid;
    const floors = wallGeometry._floors;
    const split = wallGeometry._split;

    const pos = this.computePositions(
      ellipsoid,
      wallPositions,
      floors,
      granularity
    );

    if (!Cesium.defined(pos)) {
      return;
    }

    const floorNum = pos.floorPositions.length;

    let length = pos.floorPositions[0].length;

    let size = length * floorNum;
    const positions = new Float64Array(size);

    let positionIndex = 0;

    length /= 3;
    for (let f = 0; f < floorNum; f++) {
      const fositions = pos.floorPositions[f];

      for (let i = 0; i < length; ++i) {
        const i3 = i * 3;
        const position = Cesium.Cartesian3.fromArray(
          fositions,
          i3,
          scratchCartesian3Position1
        );

        positions[positionIndex++] = position.x;
        positions[positionIndex++] = position.y;
        positions[positionIndex++] = position.z;
      }
    }

    const attributes = new Cesium.GeometryAttributes({
      position: new Cesium.GeometryAttribute({
        componentDatatype: Cesium.ComponentDatatype.DOUBLE,
        componentsPerAttribute: 3,
        values: positions
      })
    });

    const numVertices = size / 3;

    size = 2 * (floorNum * (length - 1) + (floorNum - 1) * (split ? 5 : 2));

    const indices = Cesium.IndexDatatype.createTypedArray(numVertices, size);

    // | |   |   |   |
    const h1 = Math.floor(length * 0.1);
    const h3 = Math.floor(length * 0.3);
    const h7 = Math.floor(length * 0.7);

    if (split) {
      const fositions = pos.floorPositions[0];
      const p0 = Cesium.Cartesian3.fromArray(fositions, 0, new Cesium.Cartesian3());

      (wallGeometry as any).p1 = Cesium.Cartesian3.fromArray(fositions, h1 * 3, new Cesium.Cartesian3());
      (wallGeometry as any).p3 = Cesium.Cartesian3.fromArray(fositions, h3 * 3, new Cesium.Cartesian3());
      (wallGeometry as any).p7 = Cesium.Cartesian3.fromArray(fositions, h7 * 3, new Cesium.Cartesian3());
    }

    let edgeIndex = 0;

    for (let f = 0; f < floorNum; f++) {
      // -----------
      // -----------
      for (let i = 0; i < length - 1; i += 1) {
        const LL = f * length + i;
        const LR = LL + 1;
        const pl = Cesium.Cartesian3.fromArray(
          positions,
          LL * 3,
          scratchCartesian3Position1
        );
        const pr = Cesium.Cartesian3.fromArray(
          positions,
          LR * 3,
          scratchCartesian3Position2
        );

        if (Cesium.Cartesian3.equalsEpsilon(pl, pr, Cesium.Math.EPSILON10)) {
          continue;
        }
        indices[edgeIndex++] = LL;
        indices[edgeIndex++] = LR;
      }


      if (f < floorNum - 1) {
        const LL = f * length;
        const LR = (f + 1) * length;

        indices[edgeIndex++] = LL;
        indices[edgeIndex++] = LR;
        indices[edgeIndex++] = LL + (length - 1);
        indices[edgeIndex++] = LR + (length - 1);

        if (split) {
          indices[edgeIndex++] = LL + h1;
          indices[edgeIndex++] = LR + h1;
          indices[edgeIndex++] = LL + h3;
          indices[edgeIndex++] = LR + h3;
          indices[edgeIndex++] = LL + h7;
          indices[edgeIndex++] = LR + h7;
        }
      }
    }

    return new Cesium.Geometry({
      attributes,
      indices,
      primitiveType: Cesium.PrimitiveType.LINES,
      boundingSphere: Cesium.BoundingSphere.fromVertices(positions)
    });
  }
}

export default WeatherVolumeWallAxisGeometry;
